#include "stdafx.h"

namespace classifier_test
{

namespace vem
{

vem_concept_clustering::vem_concept_clustering()
{
}

vem_concept_clustering::vem_concept_clustering(const vem_concept_clustering& Val)
	: concept_clustering(Val)
{
}

smart_ptr<concept_clustering> vem_concept_clustering::clone() const
{
	return make_smart_ptr(new vem_concept_clustering(*this));
}

void vem_concept_clustering::set_chunk_count(int Val)
{
	Chunks.resize(Val);
}

void vem_concept_clustering::set_chunk(int I, smart_ptr<validation_dataset> VDataset, smart_ptr<validation_classifier> VClassifier, double VError)
{
	Chunks[I].vdataset = VDataset;
	Chunks[I].vclassifier = VClassifier;
	Chunks[I].verror = VError;
}

void vem_concept_clustering::compute()
{
	logger::task LTask = clogger()->fine("Concept clustering");
	clogger()->fine("# of chunks: " + to_string(Chunks.size()));
	cluster_chunks();
	for (int I = 0; I < (int)Chunks.size(); I++)
		Chunks[I].vdataset.reset(),
		Chunks[I].vclassifier.reset();
	clogger()->fine("# of concepts: " + to_string(Concepts.size()));
}

const concept_clustering::chunk& vem_concept_clustering::get_chunk(int I) const
{
	return Chunks[I];
}

int vem_concept_clustering::get_concept_count() const
{
	return Concepts.size();
}

const concept_clustering::concept& vem_concept_clustering::get_concept(int I) const
{
	return *Concepts[I];
}

const vector<int>& vem_concept_clustering::get_iteration_concept_counts() const
{
	return IterConceptCounts;
}

const vector<double>& vem_concept_clustering::get_iteration_errors() const
{
	return IterErrors;
}

void vem_concept_clustering::cluster_chunks()
{
	logger::task LTask = clogger()->fine("Performing iteration");
	for (int I = 0; I < (int)Chunks.size(); I++)
		Chunks[I].concept_ptr = 0;
	double Error = numeric_limits<double>::infinity();
	for (;;)
	{
		LTask.progress(
			to_string(IterErrors.size()) + "th iteration, " +
			to_string(Concepts.size()) + " concepts, error=" + to_string((float)Error));
		if (!perform_assignment()) break;
		update_model();
		Error = get_current_error();
		IterConceptCounts.push_back(Concepts.size());
		IterErrors.push_back(Error);
	}
	LTask.progress("");
	clogger()->fine("# of iterations: " + to_string(IterErrors.size()));
	clogger()->fine("validation error: " + to_string((float)Error));
	for (int I = 0; I < (int)Concepts.size(); I++)
		for (int J = 0; J < (int)Concepts[I]->chunks.size(); J++)
			Chunks[Concepts[I]->chunks[J]].concept = I;
}

bool vem_concept_clustering::perform_assignment()
{
	bool Updated = false;
	vector<int> List(Chunks.size());
	for (int I = 0; I < (int)Chunks.size(); I++) List[I] = I;
	shuffle(List.begin(), List.end());
	for (int I = 0; I < (int)Chunks.size(); I++)
	{
		int ChunkID = List[I];
		int BestConcept = -1;
		double BestError = Chunks[ChunkID].verror;
		for (int J = 0; J < (int)Concepts.size(); J++)
		{
			double Temp = Concepts[J]->vclassifier->test_error(&*Chunks[ChunkID].vdataset);
			if (better_concept(J, Temp, BestConcept, BestError))
			{
				BestConcept = J;
				BestError = Temp;
			}
		}
		if (BestConcept < 0)
		{
			Concepts.push_back(new_single_concept(ChunkID));
			BestConcept = Concepts.size() - 1;
		}
		if (Chunks[ChunkID].concept_ptr != &*Concepts[BestConcept])
			Updated = true;
		Chunks[ChunkID].concept_ptr = &*Concepts[BestConcept];
	}
	return Updated;
}

void vem_concept_clustering::update_model()
{
	vector<int> List;
	for (int I = (int)Concepts.size() - 1; I >= 0; I--)
	{
		List.clear();
		for (int J = 0; J < (int)Chunks.size(); J++)
			if (Chunks[J].concept_ptr == &*Concepts[I])
				List.push_back(J);
		if (List == Concepts[I]->chunks) continue;
		if (List.empty())
		{
			Concepts.erase(Concepts.begin() + I);
			continue;
		}
		Concepts[I]->chunks = List;
		Concepts[I]->vdataset = Chunks[List[0]].vdataset->clone();
		for (int J = 1; J < (int)List.size(); J++)
			Concepts[I]->vdataset->add(&*Chunks[List[J]].vdataset);
		Concepts[I]->verror = Concepts[I]->vclassifier->test_error(&*Concepts[I]->vdataset);
		smart_ptr<validation_classifier> VClassifier = Concepts[I]->vdataset->train(&*BaseClassifier);
		double VError = VClassifier->test_error(&*Concepts[I]->vdataset);
		if (VError > Concepts[I]->verror) continue;
		Concepts[I]->vclassifier = VClassifier;
		Concepts[I]->verror = VError;
	}
}

smart_ptr<vem_concept_clustering::my_concept> vem_concept_clustering::new_single_concept(int ChunkID)
{
	smart_ptr<my_concept> Concept(new my_concept);
	Concept->vdataset = Chunks[ChunkID].vdataset->clone();
	Concept->vclassifier = Chunks[ChunkID].vclassifier->clone();
	Concept->verror = Chunks[ChunkID].verror;
	Concept->chunks.assign(1, ChunkID);
	return Concept;
}

bool vem_concept_clustering::better_concept(int ConA, double ErrorA, int ConB, double ErrorB)
{
	if (fabs(ErrorA - ErrorB) > 1e-6) return ErrorA < ErrorB;
	int SizeA = ConA < 0 ? 0 : Concepts[ConA]->vdataset->size();
	int SizeB = ConB < 0 ? 0 : Concepts[ConB]->vdataset->size();
	if (SizeA != SizeB) return SizeA > SizeB;
	return ConA < ConB;
}

double vem_concept_clustering::get_current_error()
{
	if (Concepts.empty()) return numeric_limits<double>::max();
	double Errors = 0;
	int Size = 0;
	for (int I = 0; I < (int)Concepts.size(); I++)
		Errors += Concepts[I]->verror * Concepts[I]->vdataset->size(),
		Size += Concepts[I]->vdataset->size();
	return Errors / Size;
	
}

}

}
